home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 23 / Amiga Format AFCD23 (Feb 1998, Issue 107).iso / -seriously_amiga- / shareware / programming / c / asyncio / src / seekasync.c < prev    next >
C/C++ Source or Header  |  1997-12-01  |  10KB  |  307 lines

  1. #include "async.h"
  2.  
  3.  
  4. static ULONG
  5. GetFileSize( AsyncFile *file, LONG *size )
  6. {
  7. #ifdef ASIO_NOEXTERNALS
  8.     struct DosLibrary    *DOSBase = file->af_DOSBase;
  9. #endif
  10.     D_S( struct FileInfoBlock, fib );
  11.  
  12.     if( !ExamineFH( file->af_File, fib ) )
  13.     {
  14.         AS_RecordSyncFailure( file );
  15.         return( FALSE );
  16.     }
  17.  
  18.     *size = fib->fib_Size;
  19.     return( TRUE );
  20. }
  21.  
  22.  
  23. _LIBCALL LONG
  24. SeekAsync( _REG( a0 ) AsyncFile *file, _REG( d0 ) LONG position, _REG( d1 ) SeekModes mode )
  25. {
  26. #ifdef ASIO_NOEXTERNALS
  27.     struct DosLibrary    *DOSBase = file->af_DOSBase;
  28. #endif
  29.     LONG    current, target, roundTarget, filePos;
  30.     LONG    minBuf, maxBuf, bytesArrived, diff;
  31.     LONG    fileSize;
  32.  
  33.     bytesArrived = AS_WaitPacket( file );
  34.  
  35.     /* MH: No packets can be pending here! */
  36.  
  37.     if( bytesArrived < 0 )
  38.     {
  39.         /* MH: Experimental: Try to allow "resume" of seeks past EOF. */
  40.  
  41.         if( file->af_SeekPastEOF )
  42.         {
  43.             /* MH: Restore saved values, to make resume possible */
  44.             bytesArrived = file->af_LastRes1;
  45.             file->af_BytesLeft = file->af_LastBytesLeft;
  46.         }
  47.         else
  48.         {
  49.             return( -1 );
  50.         }
  51.     }
  52.  
  53.     if( file->af_ReadMode )
  54.     {
  55.         /* figure out what the actual file position is */
  56.         filePos = Seek( file->af_File, 0, OFFSET_CURRENT );
  57.  
  58.         if( filePos < 0 )
  59.         {
  60.             AS_RecordSyncFailure( file );
  61.             return( -1 );
  62.         }
  63.  
  64.         /* figure out what the caller's file position is */
  65.         current = filePos - ( file->af_BytesLeft + bytesArrived ) + file->af_SeekOffset;
  66.  
  67.         /* MH: We can't clear af_SeekOffset here. If another seek is done
  68.          * directly after this one, it would mean that we will both return
  69.          * the wrong position, and start reading from the wrong position.
  70.          */
  71.         /* file->af_SeekOffset = 0; */
  72.  
  73.         /* figure out the absolute offset within the file where we must seek to */
  74.         if( mode == MODE_CURRENT )
  75.         {
  76.             target = current + position;
  77.         }
  78.         else if( mode == MODE_START )
  79.         {
  80.             target = position;
  81.         }
  82.         else /* if( mode == MODE_END ) */
  83.         {
  84.             if( !GetFileSize( file, &fileSize ) )
  85.             {
  86.                 return( -1 );
  87.             }
  88.  
  89.             target = fileSize + position;
  90.         }
  91.  
  92.         /* MH: Here we must be able to handle two different situations:
  93.          * 1) A seek directly after having dropped both buffers, and started
  94.          *    refilling (typical case: File open).
  95.          * 2) Other seeks (typical case: A seek after some initial reading).
  96.          *
  97.          * We need to subtract with "af_Buffers[ 1 - file->af_CurrentBuf ]",
  98.          * as af_CurrentBuf refers to the *arrived* buffer, not the one we're
  99.          * currently reading from (and af_Offset points into the buffer we're
  100.          * reading from)!
  101.          *
  102.          * In case 1, there will be only one packet received. af_CurrentBuf
  103.          * will be zero, and refers to the newly arrived buffer (as it
  104.          * should). For proper behaviour in the minBuf calculation, we have
  105.          * set af_Offset to point to af_Buffers[ 1 ], when starting reading
  106.          * to empty buffers. That way wee need no special case code here.
  107.          * ReadAsync() can handle this, as af_BytesLeft == 0 in that case.
  108.          */
  109.  
  110.         /* figure out what range of the file is currently in our buffers */
  111.         minBuf = current - ( LONG ) ( file->af_Offset - file->af_Buffers[ 1 - file->af_CurrentBuf ] );
  112.         maxBuf = current + file->af_BytesLeft + bytesArrived;  /* WARNING: this is one too big */
  113.  
  114.         diff = target - current;
  115.  
  116. #ifdef DO_SOME_DEBUG
  117.         Printf( "Target: %ld, minBuf: %ld, maxBuf: %ld, current: %ld, diff: %ld, bytesLeft: %ld\n",
  118.             target, minBuf, maxBuf, current, diff, file->af_BytesLeft );
  119. #endif
  120.  
  121.         if( ( target < minBuf ) || ( target >= maxBuf ) )
  122.         {
  123.             /* the target seek location isn't currently in our buffers, so
  124.              * move the actual file pointer to the desired location, and then
  125.              * restart the async read thing...
  126.              */
  127.  
  128.             if( target >= maxBuf )
  129.             {
  130.                 /* MH: There's a fair chance that we really tried to seek
  131.                  * past EOF. In order to tell for sure, we need to compare
  132.                  * the seek target with the file size. The roundTarget may
  133.                  * be before real EOF, so the "real" Seek() call might
  134.                  * not notice any problems.
  135.                  */
  136.                 if( !GetFileSize( file, &fileSize ) )
  137.                 {
  138.                     return( -1 );
  139.                 }
  140.  
  141.                 if( target > fileSize )
  142.                 {
  143.                     /* MH: Experimental: Try to allow "resume" of
  144.                      * seeks past EOF.
  145.                      */
  146.                     file->af_SeekPastEOF = TRUE;
  147.  
  148.                     SetIoErr( ERROR_SEEK_ERROR );
  149.                     AS_RecordSyncFailure( file );
  150.                     return( -1 );
  151.                 }
  152.             }
  153.  
  154.             /* this is to keep our file reading block-aligned on the device.
  155.              * block-aligned reads are generally quite a bit faster, so it is
  156.              * worth the trouble to keep things aligned
  157.              */
  158.             roundTarget = ( target / file->af_BlockSize ) * file->af_BlockSize;
  159.  
  160.             if( Seek( file->af_File, roundTarget - filePos, OFFSET_CURRENT ) < 0 )
  161.             {
  162.                 AS_RecordSyncFailure( file );
  163.                 return( -1 );
  164.             }
  165.  
  166.             AS_SendPacket( file, file->af_Buffers[ 0 ] );
  167.  
  168.             file->af_SeekOffset    = target - roundTarget;
  169.             file->af_BytesLeft    = 0;
  170.             file->af_CurrentBuf    = 0;
  171.  
  172.             /* MH: We set af_Offset to the buffer not being filled, to be able to
  173.              * handle a new seek directly after this one (see above; minBuf
  174.              * calculation). If we start reading after this seek, ReadAsync()
  175.              * will handle everything correctly, as af_BytesLeft == 0.
  176.              */
  177.             file->af_Offset        = file->af_Buffers[ 1 ];
  178.         }
  179.         else if( ( target < current ) || ( diff <= file->af_BytesLeft ) )
  180.         {
  181.             /* one of the two following things is true:
  182.              *
  183.              * 1. The target seek location is within the current read buffer,
  184.              * but before the current location within the buffer. Move back
  185.              * within the buffer and pretend we never got the pending packet,
  186.              * just to make life easier, and faster, in the read routine.
  187.              *
  188.              * 2. The target seek location is ahead within the current
  189.              * read buffer. Advance to that location. As above, pretend to
  190.              * have never received the pending packet.
  191.              */
  192.  
  193.             AS_RequeuePacket( file );
  194.  
  195.             file->af_BytesLeft    -= diff;
  196.             file->af_Offset        += diff;
  197.  
  198.             /* MH: We don't need to clear the seek offset here, since
  199.              * if we get here, we must have read some data from the current
  200.              * buffer, and af_SeekOffset will be zero then (done by
  201.              * ReadAsync()).
  202.              */
  203.  
  204.             /* MH: If we're recovering from seek past EOF, restore some
  205.              * values here.
  206.              */
  207.             if( file->af_SeekPastEOF )
  208.             {
  209.                 file->af_Packet.sp_Pkt.dp_Res1 = file->af_LastRes1;
  210.             }
  211.         }
  212.         else
  213.         {
  214.             /* at this point, we know the target seek location is within
  215.              * the buffer filled in by the packet that we just received
  216.              * at the start of this function. Throw away all the bytes in the
  217.              * current buffer, send a packet out to get the async thing going
  218.              * again, readjust buffer pointers to the seek location, and return
  219.              * with a grin on your face... :-)
  220.              */
  221.  
  222.             /* MH: Don't refill the buffer we just got, but the other one! */
  223.             AS_SendPacket( file, file->af_Buffers[ 1 - file->af_CurrentBuf ] );
  224.  
  225.             /* MH: Account for bytes left in buffer we drop *and* the af_SeekOffset.
  226.              */
  227.             diff -= file->af_BytesLeft - file->af_SeekOffset;
  228.  
  229.             /* MH: Set the offset into the current (newly arrived) buffer */
  230.             file->af_Offset = file->af_Buffers[ file->af_CurrentBuf ] + diff;
  231.             file->af_BytesLeft = bytesArrived - diff;
  232.  
  233.             /* MH: We need to clear the seek offset here, since we can't do it above.
  234.              */
  235.             file->af_SeekOffset = 0;
  236.  
  237.             /* MH: This "buffer switching" is important to do. It wasn't done!
  238.              * This explains the errors one could encounter now and then.
  239.              * The AS_SendPacket() call above is not the cause, and *is* correct.
  240.              */
  241.             file->af_CurrentBuf = 1 - file->af_CurrentBuf;
  242.         }
  243.     }
  244.     else
  245.     {
  246.         /* flush the buffers */
  247.         if( file->af_BufferSize > file->af_BytesLeft )
  248.         {
  249.             if( Write(
  250.                 file->af_File,
  251.                 file->af_Buffers[ file->af_CurrentBuf ],
  252.                 file->af_BufferSize - file->af_BytesLeft ) < 0 )
  253.             {
  254.                 AS_RecordSyncFailure( file );
  255.                 return( -1 );
  256.             }
  257.         }
  258.  
  259.         /* this will unfortunately generally result in non block-aligned file
  260.          * access. We could be sneaky and try to resync our file pos at a
  261.          * later time, but we won't bother. Seeking in write-only files is
  262.          * relatively rare (except when writing IFF files with unknown chunk
  263.          * sizes, where the chunk size has to be written after the chunk data)
  264.          */
  265.  
  266.         /* MH: Ideas on how to improve the above (not tested, since I don't need
  267.          * the SeekAsync for writing in any of my programs at the moment! ;):
  268.          *
  269.          * Add a new field to the AsyncFile struct. af_WriteOffset or something like
  270.          * that (af_SeekOffset can probably be used). Like in the read case, we
  271.          * calculate a roundTarget, but we don't seek to that (but rather to the
  272.          * "absolute" position), and save the difference in the struct. af_BytesLeft
  273.          * and af_Offset are adjusted to point into the "middle" of the buffer,
  274.          * where the write will occur. Writes then needs some minor changes:
  275.          * Instead of simply writing the buffer from the start, we add the offset
  276.          * (saved above) to the buffer base, and write the partial buffer. The
  277.          * offset is then cleared. Voila: The file is still block-aligned, at the
  278.          * price of some non-optimal buffer usage.
  279.          *
  280.          * Problem: As it is now, Arg3 in the packet is always set to the buffer size.
  281.          * With the above fix, this would have to be updated for each SendPacket (i.e.
  282.          * a new argument would be needed).
  283.          */
  284.  
  285.         current = Seek( file->af_File, position, mode );
  286.  
  287.         if( current < 0 )
  288.         {
  289.             AS_RecordSyncFailure( file );
  290.             return( -1 );
  291.         }
  292.  
  293.         file->af_BytesLeft    = file->af_BufferSize;
  294.         file->af_CurrentBuf    = 0;
  295.         file->af_Offset        = file->af_Buffers[ 0 ];
  296.     }
  297.  
  298.     if( file->af_SeekPastEOF )
  299.     {
  300.         /* MH: Clear up any error flags, and restore last Res1. */
  301.         file->af_SeekPastEOF = FALSE;
  302.     }
  303.  
  304.     SetIoErr( 0 );
  305.     return( current );
  306. }
  307.